//***************************************************************************************
//
//! \file cmd_server.c
//! Implementation of the commands in the ftp server.
//!
//! \author    wgy, sc
//! \version   V1.0
//! \date      2022-11-19
//! \copyright GNU Public License V3.0
//
//***************************************************************************************
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <dirent.h>
#include <time.h>
#include <fcntl.h>
#include "ftp_server.h"
#include "cmd_server.h"
#include "config.h"
/// @brief 拼接当前工作目录和给出的参数，得到完整路径。
/// @param src 将要存放得到的路径字符串的位置。
/// @param file 具体文件名的字符串指针。
void set_file_dir(char* src, char* file){
	getcwd(src, DIR_INFO);
	int dirlength = strlen(src);
	if (file[0] != '/')
		src[dirlength] = '/';
	if (file[0] != '/')
	{
		src[dirlength] = '/';
		src[dirlength + 1] = '\0';
	} else {
		src[dirlength] = '\0';
	}

	strcat(src, file);
}
/// @brief 从客户端给出的信息中提取规范化的参数，供之后的具体操作使用。
/// @param msg 服务端配置参数的指针，此处使用接收到的客户端消息数据。
/// @param cmd_arg 存放规范化参数的位置。
/// @param bit 命令的位数。参数将会从命令之后开始读取。
void set_cmd_arg(Msg* msg, char* cmd_arg, int bit){
	int length = strlen(msg->control_info);
	int i = 0;
	for (i = bit; i < length; i++)
	{
		cmd_arg[i - bit] = msg->control_info[i];
	}
	cmd_arg[i - bit] = '\0';
}
/// @brief 打印当前工作目录。
/// @param client_sock 连接客户端的套接字描述符。
/// @param msg 服务端配置参数的指针。
void server_list(int client_sock, Msg *msg)
{
	struct sockaddr_in client;
	int sin_size = sizeof(struct sockaddr_in);
	FILE *pipe_fp;
	char t_dir[DIR_INFO];
	char list_cmd_info[DIR_INFO];
	snprintf(list_cmd_info, DIR_INFO, "ls -l %s", getcwd(t_dir, DIR_INFO));
	if ((pipe_fp = popen(list_cmd_info, "r")) == NULL)
	{
		printf("pipe open error in cmd_list\n");
		return;
	}
	printf("pipe open successfully!, cmd is %s\n", list_cmd_info);
	char t_char;
	while ((t_char = fgetc(pipe_fp)) != EOF)
	{
		printf("%c", t_char);
		write(msg->ftp_data_sock, &t_char, 1);
	}
	write(msg->ftp_data_sock, "task done", 9);
	pclose(pipe_fp);
	printf("close pipe successfully!\n");
}
/// @brief 向服务端发送文件，可选择ASCII和Binary两种模式。若文件不存在，将会报错。
/// @param client_sock 连接客户端的套接字描述符。
/// @param msg 服务端配置参数的指针。
void server_put(int client_sock, Msg *msg)
{
	// send_message(client_sock, serverInfo150, "");
	char format_client_info[MAX_INFO];
	char file_info[DIR_INFO]; 
	set_cmd_arg(msg, format_client_info,5);
	set_file_dir(file_info, format_client_info);
	printf("%s\n", file_info);

	FILE *fp = fopen(file_info, "wb");
	if (fp == NULL) {
		printf("Open file error: %s\r\n", strerror(errno));
		char cwd_info[MSG_INFO];
		snprintf(cwd_info, MSG_INFO, "550 %s : %s\r\n", format_client_info, strerror(errno));
		send_message(client_sock, cwd_info, "");
		return;
	}

	send_message(client_sock, "task done", "");
	bzero(msg->control_info, MAX_INFO);
	recv_message(client_sock);
	int size = 0;
	if (strncmp("SIZE", msg->control_info, 4) == 0 || strncmp("size", msg->control_info, 4) == 0)
	{
		char *cmd = msg->control_info + 5;
		size = atoi(cmd);
	}

	char data_info[MAX_INFO];
	memset(data_info, 0, MAX_INFO);
	int n;
	int cmd_sock = fileno(fp);
	while ((n = read(msg->ftp_data_sock, data_info, MAX_INFO > size ? size : MAX_INFO)) > 0)
	{
		size -= n;
		if (write(cmd_sock, data_info, n) != n){
			printf("Stor transfer error!\n");
			return;
		}
		if (size <= 0) break;
	}
	fclose(fp);
	umask(0);
	chmod(file_info, 0777);
}
/// @brief 从服务端获取文件，可选择ASCII和Binary两种模式。若文件不存在，将会报错。
/// @param client_sock 连接客户端的套接字描述符。
/// @param msg 服务端配置参数的指针。
void server_get(int client_sock, Msg *msg)
{
	if (msg->control_info[5] == 'a') msg->file_type = ASCII;
	else if (msg->control_info[5] == 'b') msg->file_type = BINARY;

	char format_client_info[MAX_INFO];
	char file_info[DIR_INFO];
	set_cmd_arg(msg, format_client_info, 7);
	set_file_dir(file_info, format_client_info);
	printf("1");
	struct stat test_exist;
	if (stat(file_info, &test_exist) < 0) {
		char message[MSG_INFO];
		sprintf(message, "550 server: file %s doesn't exist!\r\n\0", file_info);
		send_message(client_sock, message, "\0");
		return;
	} else {
		char message[MSG_INFO];
		sprintf(message, "220 server: file %s exist.\r\n", file_info);
		send_message(client_sock, message, "\0");
	}



	FILE *fp;
	if (msg->file_type == BINARY) fp = fopen(file_info, "rb");
	else fp = fopen(file_info, "rt");
	if (fp == NULL)
	{
		printf("Open file error: %s\r\n", strerror(errno));
		char cwd_info[MSG_INFO];
		snprintf(cwd_info, MSG_INFO, "%s : %s\r\n", format_client_info, strerror(errno));
		send_message(client_sock, cwd_info, "\0");
		fclose(fp);
		return;
	}

	int cmd_sock = fileno(fp); //获取文件描述词
	int size;
	struct stat file_stat;
	int ret = fstat(cmd_sock, &file_stat); // 获取文件状态
	size = file_stat.st_size;
	char s[50];
	sprintf(s, "%d\0\0", size);
	recv_message(client_sock);
	if (strncmp("SIZE", msg->control_info, 4) == 0 || strncmp("size", msg->control_info, 4) == 0){
		send(client_sock, s, strlen(s), 0);
	}
	printf("size:%d\n", size);

	int n;
	char data_info[MAX_INFO];
	memset(data_info, 0, MAX_INFO);
	if (msg->file_type == BINARY){
		while ((n = read(cmd_sock, data_info, MAX_INFO > size ? size : MAX_INFO)) > 0){
			size -= n;
			if (write(msg->ftp_data_sock, data_info, n) != n) {
				printf("Stor transfer error!\n");
				return;
			}
			if (size <= 0)
				break;
		}
	} else if (msg->file_type == ASCII) {
		char c;
		while ((c = fgetc(fp)) != EOF){
			write(msg->ftp_data_sock, &c, 1);
		}
	}
	fclose(fp);
	umask(0);
	chmod(file_info, 0777);
}
/// @brief 根据客户端给出的参数，创建相应目录。若目录已存在，将会报错。
/// @param client_sock 连接客户端的套接字描述符。
/// @param msg 服务端配置参数的指针。
void server_mkd(int client_sock, Msg *msg)
{

	char format_client_info[MAX_INFO] = "";
	char client_dir[DIR_INFO];
	set_cmd_arg(msg, format_client_info, 4);
	set_file_dir(client_dir, format_client_info);
	
	umask(0);
	char mkd_info[MSG_INFO] = "";
	if (mkdir(client_dir, 0777) >= 0){
		printf("\"%s\" is create successfully.\r\n", client_dir);
	}
	else {
		snprintf(mkd_info, MSG_INFO, "550 %s : %s\r\n", client_dir, strerror(errno));
		perror("mkdir():");
		send_message(client_sock, mkd_info, "\0");
	}
}
/// @brief 根据客户端给出的参数，删除相应目录。若目录不存在，将会报错。
/// @param client_sock 连接客户端的套接字描述符。
/// @param msg 服务端配置参数的指针。
void server_rmd(int client_sock, Msg *msg)
{

	char format_client_info[MAX_INFO];
	char client_dir[DIR_INFO];
	set_cmd_arg(msg, format_client_info, 4);
	set_file_dir(client_dir, format_client_info);

	// client_dir目录，成功输出消息，否则报错。
	char rmd_info[MSG_INFO] = "";
	if (rmdir(client_dir) >= 0){
		printf("\"%s\" is deleted successfully.\r\n", client_dir);
	}
	else {
		snprintf(rmd_info, MSG_INFO, "%s :%s\r\n", client_dir, strerror(errno));
		perror("rmdir():");
		send_message(client_sock, rmd_info, "\0");
	}
}
/// @brief 根据客户端给出的参数，删除相应文件。若文件不存在，将会报错。
/// @param client_sock 连接客户端的套接字描述符。
/// @param msg 服务端配置参数的指针。
void server_del(int client_sock, Msg *msg)
{
	char format_client_info[MAX_INFO];
	char client_dir[DIR_INFO];

	set_cmd_arg(msg, format_client_info, 5);
	set_file_dir(client_dir, format_client_info);

	char del_info[MSG_INFO];
	if (unlink(client_dir) >= 0){
		sprintf(del_info, "\"%s\" is deleted successfully.\r\n", client_dir);
		printf("%s", del_info);
		send_message(client_sock, del_info, "\0");
	}
	else {
		snprintf(del_info, MSG_INFO, "%s : %s \r\n", client_dir, strerror(errno));
		perror("unlink():");
		send_message(client_sock, del_info, "\0");
	}
}
/// @brief 根据客户端给出的参数，改变当前工作目录。
/// @param client_sock 连接客户端的套接字描述符。
/// @param msg 服务端配置参数的指针。
void server_cwd(int client_sock, Msg *msg)
{

	char format_client_info[MAX_INFO];
	char client_dir[DIR_INFO];
	set_cmd_arg(msg, format_client_info, 4);
	set_file_dir(client_dir, format_client_info);

	char cwd_info[MSG_INFO];
	char tmp_dir[MSG_INFO];
	if (chdir(client_dir) >= 0)
	{
		snprintf(cwd_info, MSG_INFO, "257 \"%s\" is current location.\r\n", getcwd(tmp_dir, DIR_INFO));
		send_message(client_sock, cwd_info, "\0");
	} else {
		snprintf(cwd_info, MSG_INFO, "%s :%s\r\n", client_dir, strerror(errno));
		perror("chdir():");
		send_message(client_sock, cwd_info, "\0");
	}
}
/// @brief 向客户端发送目前所在的工作目录。
/// @param client_sock 连接客户端的套接字描述符。
/// @param msg 服务端配置参数的指针。
void server_pwd(int client_sock, Msg *msg)
{
	char pwd_info[MSG_INFO];
	char tmp_dir[DIR_INFO];

	snprintf(pwd_info, MSG_INFO, "current location: \"%s\".\r\n", getcwd(tmp_dir, DIR_INFO));
	send_message(client_sock, pwd_info, "\0");
}
/// @brief 处理PORT连接的请求。扫描客户端发送的地址信息并连接，生成传递数据的套接字。
/// @param client_sock 连接客户端的套接字描述符。
/// @param msg 服务端配置参数的指针。
void handle_port(int client_sock, Msg *msg)
{
	struct sockaddr_in addr_data, server_data;
	msg->ftp_data_sock = socket(AF_INET, SOCK_STREAM, 0);
	server_data.sin_family = AF_INET;
	server_data.sin_addr.s_addr = htonl(INADDR_ANY);
	server_data.sin_port = htons(20);
	int set = bind(msg->ftp_data_sock, (struct sockaddr *)&server_data, sizeof(server_data));
	if (set != 0 && errno == 11)
	{
		send_message(client_sock, "port mode can't work, please change.\n","\0");
		return;
	}

	// local variables
	unsigned char act_port[2];
	int act_ip[4], port_dec;
	char ip_decimal[40];
	sscanf(msg->control_info, "PORT %d,%d,%d,%d,%d,%d", &act_ip[0], &act_ip[1], &act_ip[2], &act_ip[3], (int *)&act_port[0], (int *)&act_port[1]);
	addr_data.sin_family = AF_INET; // local_data_addr_act
	sprintf(ip_decimal, "%d.%d.%d.%d", act_ip[0], act_ip[1], act_ip[2], act_ip[3]);
	printf("IP is %s\n", ip_decimal);
	addr_data.sin_addr.s_addr = inet_addr(ip_decimal);
	port_dec = act_port[0] * 256 + act_port[1];
	printf("port %d\n", port_dec);
	addr_data.sin_port = htons(port_dec);

	//服务器主动向客户端发送连接请求
	if (connect(msg->ftp_data_sock, (struct sockaddr *)&addr_data, (int)sizeof(struct sockaddr)) != 0)
	{
		printf("trying connection in %s %d\n", inet_ntoa(addr_data.sin_addr), ntohs(addr_data.sin_port));
		send_message(client_sock, serverInfo425, "\0");
		close(msg->ftp_data_sock);
	}
	else
	{
		send_message(client_sock, serverInfo200, "\0");
		printf("Data connection to client created (active connection) \n");
	}
}
/// @brief 在创建PASV连接时使用，生成传递数据所需要的套接字并完成绑定和监听。
/// @param sock 将要更改的套接字变量的指针。无需初始化。
/// @return 配置好的用户sockaddr_in信息。
struct sockaddr_in create_data_sock(int *sock)
{
	struct sockaddr_in t_data_addr;
	*sock = socket(AF_INET, SOCK_STREAM, 0);
	if (*sock < 0)
	{
		perror("Creating data socket error!\n");
	}
	srand((int)time(0));
	int a = rand() % 1000 + 1025;
	bzero(&t_data_addr, sizeof(t_data_addr));
	t_data_addr.sin_family = AF_INET;
	t_data_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	t_data_addr.sin_port = htons(a);
	if (bind(*sock, (struct sockaddr *)&t_data_addr, sizeof(struct sockaddr)) < 0)
	{
		perror("Bind error in create data socket:");
	}
	listen(*sock, 1);
	return t_data_addr;
}
/// @brief 处理PASV连接的请求。连接成功后，将向客户端发送相应的地址信息，并生成传递数据的套接字。
/// @param client_sock 连接客户端的套接字描述符。
/// @param msg 服务端配置参数的指针。
void handle_pasv(int client_sock, Msg *msg)
{
	char pasv_msg[MSG_INFO];
	char port_str[8];

	struct sockaddr_in user_data_addr;

	int data_sock;
	user_data_addr = create_data_sock(&data_sock);

	int tmp_port1;
	int tmp_port2;

	tmp_port1 = ntohs(user_data_addr.sin_port) / 256;
	tmp_port2 = ntohs(user_data_addr.sin_port) % 256;

	printf("PASV,端口号：%d\n", ntohs(user_data_addr.sin_port));

	snprintf(port_str, sizeof(port_str), ",%d,%d", tmp_port1, tmp_port2);
	snprintf(pasv_msg, MSG_INFO, "227 Entering Passive Mode %s.\r\n", port_str);

	send_message(client_sock, pasv_msg, "\0");

	int set = sizeof(user_data_addr);
	msg->ftp_data_sock = accept(data_sock, (struct sockaddr *)&user_data_addr, (socklen_t *)&set);
}